package com.gzhryc.jdbc;

import com.gzhryc.jdbc.log.Logger;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;

public class JdbcSession {
	
	static Logger log = Logger.getLogger(JdbcSession.class);
	
	//线程的静态公共对象
	private static ThreadLocal<Session> SessionThreadLocal = new ThreadLocal<Session>();

	public static Boolean showLog = false;

	/**
	 * 事务开始,线程安全
	 */
	public static void begin(){
		Session session = SessionThreadLocal.get();
		if(session == null){
			session = new Session();
			SessionThreadLocal.set(session);
		}
		session.begin();
		if(showLog) {log.info("～～～启用事务～～～");}
	}
	
	/**
	 * 结束线程，线程安全
	 */
	public static void end(){
		Session session = SessionThreadLocal.get();
		if(session != null){
			try {
				session.end();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			if(session.isEnd()) {
				session.clear();
				//结束则移除
				SessionThreadLocal.remove();
			}
		}
	}
	
	/**
	 * 提交会话
	 */
	public static void commit() throws SQLException{
		Session session = SessionThreadLocal.get();
		if(session != null){
			session.commit();
		}
	}
	
	/**
	 * 回滚会话
	 */
	public static void rollback(){
		Session session = SessionThreadLocal.get();
		if(session != null){
			try {
				session.rollback();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 设置保存点
	 */
	public static void setSavepoint(){
		Session session = SessionThreadLocal.get();
		if(session != null){
			session.setSavepoint();
		}
	}
	
	/**
	 * 获取会话链接
	 * @param connKey
	 * @return
	 * @throws SQLException
	 */
	public static Connection getConn(String connKey) throws SQLException{
		Session session = SessionThreadLocal.get();
		if(session != null){
			return session.getConn(connKey);
		}
		return null;
	}
	
	/**
	 * 获取当前线程Session会话的数量
	 * @return
	 */
	public static Integer getSessionCount() {
		Session session = SessionThreadLocal.get();
		if(session != null){
			return session.getSessionCount();
		}
		return 0;
	}
	
	public static void clear() {
		Session session = SessionThreadLocal.get();
		if(session != null){
			session.clear();
			SessionThreadLocal.remove();
		}
	}
	
	public static void newSession(Runnable runnable) {
		Thread thread = new Thread(runnable);
		thread.start();
	}
	
	/**
	 * 事务，可支持多个链接，目的是处理多数据源
	 * @author 张浩
	 * @version 2017-12-17
	 */
	private static class Session {
		
		private Map<String,Connection> connStore;
		private ArrayBlockingQueue<SessionStatus> queue;				//最大支持30个回话嵌套
		private boolean createSavepoint;
		
		public Session(){
			this.connStore = new HashMap<String,Connection>();
			this.queue = new ArrayBlockingQueue<SessionStatus>(30);
			this.createSavepoint = false;
		}
		
		/**
		 * 获取链接
		 * @param connKey
		 * @return
		 * @throws SQLException
		 */
		public Connection getConn(String connKey) throws SQLException{
			Connection conn = connStore.get(connKey);
			if(conn == null && this.queue.size() > 0) {
				conn = ConnectionFactory.getConn(connKey, false);
				connStore.put(connKey, conn);
			}
			if(conn != null) {
				SessionStatus status = this.queue.peek();
				assert status != null;
				if(status.getCreateSavepoint()) {
					if(!status.getSavepointStore().containsKey(connKey)) {
						status.getSavepointStore().put(connKey, conn.setSavepoint());
					}
				}
			}
			return conn;
		}
		
		public void begin(){
			SessionStatus status = new SessionStatus();
			if(this.createSavepoint) {
				status.setCreateSavepoint(true);
				this.createSavepoint = false;
			}else {
				//继承
				SessionStatus upStatus = this.queue.peek();
				if(upStatus != null) {
					status.setCreateSavepoint(upStatus.getCreateSavepoint());
					status.setSavepointStore(upStatus.getSavepointStore());
				}
			}
			try {
				this.queue.put(status);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if(showLog) {log.info("～～事务开始，个数：" + this.queue.size());}
		}
		
		public void commit() throws SQLException{
			SessionStatus status = this.queue.peek();
			assert status != null;
			status.setIsToOperate(true);
			status.setIsToCommit(status.getUpTheCommit());
			if(this.queue.size() == 1) {
				//已经是最外层，进行提交
				if(status.getIsToCommit()) {
					Iterator<Connection> iterator = connStore.values().iterator();
					while(iterator.hasNext()){
						//TODO 需要处理中途提交失败，前面提交的事务需要回滚
						Connection conn = iterator.next();
						conn.commit();
						conn.close(); //最外层，则关闭连接
					}
					this.connStore.clear();
				}
			}
			if(showLog) {log.info("～～事务提交，个数：" + this.queue.size());}
		}
		
		public void rollback() throws SQLException{
			SessionStatus status = this.queue.peek();
			status.setIsToOperate(true);
			status.setIsToCommit(false);
			if(this.queue.size() == 1) {
				//已经是最外层，进行回滚
				if(status.getIsToCommit()) {
					Iterator<Connection> iterator = connStore.values().iterator();
					while(iterator.hasNext()){
						Connection conn = iterator.next();
						conn.rollback();
						conn.close(); //最外层，则关闭连接
					}
					this.connStore.clear();
				}
			}else if(status.getSavepointStore().size() > 0) {
				//存在保存点，则回滚到保存点
				for(String connKey:connStore.keySet()) {
					Savepoint savepoint = status.getSavepointStore().get(connKey);
					if(savepoint != null) {
						Connection conn = connStore.get(connKey);
						conn.rollback(savepoint);
					}
				}
			}
		}
		
		public void end() throws SQLException {
			SessionStatus status = this.queue.poll();
			assert status != null;
			if(status.getIsToOperate()) {
				//已操作，如果没有保存点，则向外传递事务结果
				if(status.getSavepointStore().size() == 0) {
					SessionStatus nextStatus = this.queue.peek();
					if(nextStatus != null) {
						nextStatus.setUpTheCommit(status.getIsToCommit());
					}
				}
			}else {
				//未操作，则默认回滚
				status.setIsToCommit(false);
				SessionStatus nextStatus = this.queue.peek();
				if(nextStatus == null) {
					//如果已经是最外层，则进行回滚
					Iterator<Connection> iterator = connStore.values().iterator();
					while(iterator.hasNext()){
						Connection conn = iterator.next();
						conn.rollback();
						conn.close(); //最外层，则关闭连接
					}
					this.connStore.clear();
				}else if(status.getSavepointStore().size() > 0) {
					//存在保存点，则回滚到保存点
					for(String connKey:connStore.keySet()) {
						Savepoint savepoint = status.getSavepointStore().get(connKey);
						if(savepoint != null) {
							Connection conn = connStore.get(connKey);
							conn.rollback(savepoint);
						}
					}
				}else {
					nextStatus.setUpTheCommit(status.getIsToCommit());
				}
			}
			if(showLog) {log.info("～～事务结束，个数：" + this.queue.size());}
		}
		
		public void setSavepoint() {
			this.createSavepoint = true;
		}
		
		/**
		 * 获取当前线程Session会话的数量
		 * @return
		 */
		public Integer getSessionCount() {
			return this.queue.size();
		}
		
		public boolean isEnd() {
			return this.queue.size() == 0;
		}
		
		public void clear(){
			boolean toRollback = !this.queue.isEmpty();
			this.queue.clear();
			Iterator<Connection> iterator = connStore.values().iterator();
			while(iterator.hasNext()){
				Connection conn = iterator.next();
				try {
					if(!conn.getAutoCommit() && toRollback) {
						conn.rollback();
					}
					if(!conn.isClosed()) {
						conn.close();
					}
				}catch(SQLException e) {
					e.printStackTrace();
				}
			}
			this.connStore.clear();
			if(showLog) {log.info("～～～事务清除～～～");}
		}
	}
	
	private static class SessionStatus {
		
		private Boolean upTheCommit;		//嵌套中的事务状态
		private Boolean isToOperate;
		private Boolean isToCommit;
		private Boolean createSavepoint;
		private Map<String,Savepoint> savepointStore;
		
		public SessionStatus() {
			this.upTheCommit = true;
			this.isToOperate = false;
			this.isToCommit = false;
			this.createSavepoint = false;
			this.savepointStore = new HashMap<String,Savepoint>();
		}
		
		public Boolean getIsToOperate() {
			return isToOperate;
		}
		public void setIsToOperate(Boolean isToOperate) {
			this.isToOperate = isToOperate;
		}
		public Boolean getIsToCommit() {
			return isToCommit;
		}
		public void setIsToCommit(Boolean isToCommit) {
			this.isToCommit = isToCommit;
		}

		public Map<String, Savepoint> getSavepointStore() {
			return savepointStore;
		}

		public void setSavepointStore(Map<String, Savepoint> savepointStore) {
			this.savepointStore = savepointStore;
		}

		public Boolean getUpTheCommit() {
			return upTheCommit;
		}
		public void setUpTheCommit(Boolean upTheCommit) {
			this.upTheCommit = upTheCommit;
		}
		public Boolean getCreateSavepoint() {
			return createSavepoint;
		}
		public void setCreateSavepoint(Boolean createSavepoint) {
			this.createSavepoint = createSavepoint;
		}
	}
}
